feat(rest): surface silently-dropped write fields on PATCH/POST /data (#3431)#3448
Merged
Merged
Conversation
…#3431) Wire the engine's `onFieldsDropped` strip-observability channel (#3413) through the DataProtocol and REST write path so an external API caller is no longer silently stripped when `readonly` (#2948) / `readonlyWhen` (#3042) drops caller-supplied fields. The write still succeeds — this only makes the strip observable (the same silent-success class #3407 fixed flow-side). - metadata-protocol: `updateData` collects the engine's `onFieldsDropped` events; `createData` surfaces the #3043 static-`readonly` INGRESS strip via a payload diff (that strip runs BEFORE the engine, which is INSERT-readonly- exempt, so the engine listener never sees it). Both attach an optional `droppedFields` list to the response when ≥1 field was dropped. - spec: `UpdateDataResponseSchema` / `CreateDataResponseSchema` gain an optional `droppedFields: DroppedFieldsEvent[]` — present only on a drop, so the shape stays backward-compatible for clients that only read `record`. - rest: PATCH `/data/:object/:id` and POST `/data/:object` echo drops as the `X-ObjectStack-Dropped-Fields` response header and keep the structured list on the body. Status/success semantics unchanged (200 update / 201 create). Tests: protocol passthrough (update forwards engine events; create surfaces the ingress strip; no-drop omits the key) and REST header/body (single + multi field, no-drop, create 201). Deferred (issue #3431 D2 open questions): bulk (`updateManyData` / `createManyData` / `batchData`) and GraphQL mutation wiring, typed `@objectstack/client` warnings, and adding the header to the Hono CORS `exposeHeaders` allow-list for cross-origin browser reads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNqeAmxCCq9jgLFwa9subq
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 104 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…#3431) `content/docs/references/api/protocol.mdx` is generated from the spec Zod schemas (build-docs.ts) and is checked in; the new optional `droppedFields` on Create/Update DataResponse must be regenerated so `check:docs` (run inside the TypeScript Type Check job) stays green. Generated output only — no hand-edits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNqeAmxCCq9jgLFwa9subq
os-zhuang
marked this pull request as ready for review
July 24, 2026 15:10
Open
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3431. Follow-up to #3407 / #3413.
Problem
#3413 built the engine-level strip-observability channel
(
WriteObservabilityOptions.onFieldsDropped) and wired the flow side(
update_record/create_recordemit a step warning +droppedFields). TheREST write path was never wired, so an external API caller writing N fields
still got a bare
200 + recordwhenreadonly(#2948) /readonlyWhen(#3042)stripping meant
< Nactually landed — the only way to notice was a per-fielddiff of the returned row (which need not echo every field). Same silent-success
class #3407 fixed flow-side, just on HTTP.
Design decisions
The issue left the feedback shape "待拍板" (to be decided). This PR ships both
robust channels, resolving the D1/D3 open points:
droppedFieldsevent list — the issue's own sanctioned option ("protocol 结果附带事件列表"). This is the reliable, structured, cross-origin-safe surface and
feeds every protocol consumer, not just REST.
X-ObjectStack-Dropped-Fieldsresponse header (zero body-contract intrusion,matching the existing
X-Export-Styles: droppedprecedent).Status/success semantics are unchanged (200 update / 201 create) — a strip
is legitimate semantics, not a failure (same principle as #3413). The FLS write
gate is untouched (already fails closed with 403).
Changes
@objectstack/metadata-protocolupdateDataregisters anonFieldsDroppedcollector onengine.updateandreturns the events as
droppedFields.createDatasurfaces the 安全/设计:静态 readonly 的 INSERT 豁免让审批/状态字段可在创建时被直接播种(比 #3003 少一步) #3043 static-readonlyingress strip too — thatstrip runs at the protocol ingress (
stripReadonlyForInsert), before theengine (which is INSERT-
readonly-exempt), so it is recovered by diffing thesupplied payload against the stripped one (
diffDroppedFields). The engine'sonFieldsDroppedis also wired for a future insert-side engine strip. A faultylistener never breaks the write (the engine catches + logs).
@objectstack/specUpdateDataResponseSchema/CreateDataResponseSchemagain an optionaldroppedFields: DroppedFieldsEvent[]— present only on a drop, so the shapestays backward-compatible for clients that only read
record.@objectstack/rest/data/:object/:idand POST/data/:objectecho drops as theX-ObjectStack-Dropped-Fieldsheader (field;reason=<reason>tokens,comma-joined) and keep the structured list on the body. Tolerates both the
Hono-style
res.headerand node-styleres.setHeader.Testing
protocol.dropped-fields.test.ts— update forwards engine events (single +multi-pass); create surfaces the ingress strip; no-drop omits the key; system
create keeps the field.
rest-dropped-fields.test.ts— PATCH/POST set the header + keep bodydroppedFields, multi-field/reason join, no-header when nothing dropped,create stays 201.
tsc --noEmitclean for the touched files.
Out of scope (issue #3431 D2 open questions, deferred)
updateManyData/createManyData/batchDataand GraphQLmutation.
@objectstack/clientwarnings (the bodydroppedFieldsis alreadyreadable; typing it is a follow-up).
exposeHeadersallow-list for cross-originbrowser reads (three-site lockstep) — the body
droppedFieldsis thecross-origin-safe channel meanwhile.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NNqeAmxCCq9jgLFwa9subq
Generated by Claude Code